/***************************************************************************
 *   Copyright (C) 2007 by Ivan Vasić              *
 *   ivasic@gmail.com                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "mysqlfactory.h"
#include "mysqlannouncehandler.h"
#include "mysqltorrents.h"
#include "mysqlusers.h"
#include "mysqlmodeltorrent.h"
#include "mysqlmodeluser.h"
#include "sha1hash.h"
#include "sha1hashgen.h"
#include "exceptions.h"

#include <QtCore>
#include <QtSql>

MySQLFactory::MySQLFactory()
		: DBFactory()
{
	m_lastPeerSync = 0;
}


MySQLFactory::~MySQLFactory()
{}

AnnounceHandler* MySQLFactory::createAnnounceHandler() const
{
	return new MySQLAnnounceHandler();
}


void MySQLFactory::registerTorrent(const SHA1Hash& infohash, const QByteArray& metainfo, const QString& name, const QString& description, quint64 size, const QString& addedBy)
{
	QSqlQuery query;
	query.prepare("INSERT INTO Torrent (INFO_HASH, Name, description, Size, Added, UploadedBy, metainfo) VALUES (?,?,?,?, NOW(), ?,?)");

	query.bindValue(0, infohash.toString(), QSql::In);
	query.bindValue(1, name, QSql::In);
	query.bindValue(2, description, QSql::In);
	query.bindValue(3, size, QSql::In);
	query.bindValue(4, addedBy, QSql::In);
	query.bindValue(5, metainfo, QSql::In);

	query.exec();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
}

bool MySQLFactory::isTorrentRegistered(const SHA1Hash & infohash) const
{
	QSqlQuery query;
	query.prepare("CALL spTorrentExists(?,@v)");
	query.bindValue(0, infohash.toString(), QSql::In);
	query.exec();
	query.exec("SELECT @v");
	query.next();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);

	return query.value(0).toBool();
}


void MySQLFactory::timeout()
{
	///@TODO DeadPeer sync na svakih 10min? Ili neki drugi interval?
	if (QDateTime::currentDateTime().toTime_t() - m_lastPeerSync > 600)
	{
		//update lastPeerSync
		m_lastPeerSync = QDateTime::currentDateTime().toTime_t();

		//Pozovi proceduru za brisanje neaktivnih peer-ova
		QSqlQuery query;
		query.exec("CALL spDeleteOldPeers()");

#define _MY_DEBUG_
#ifdef _MY_DEBUG_
		qDebug() << "spDeleteOldPeers() executed.";
#endif

		if (query.lastError().type() != QSqlError::NoError)
			throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
	}
}

void MySQLFactory::registerUser(const QString & username, const QString & password, const QString & email, const QString& name)
{
	QString passkey = createPasskey(username, password);

	qDebug() << "Registering user" << username << "with passkey" << passkey;

	QSqlQuery query;
	query.prepare("CALL spRegisterUser(?,?,?,?,?)");
	query.bindValue(0, username, QSql::In);
	query.bindValue(1, password, QSql::In);
	query.bindValue(2, passkey, QSql::In);
	query.bindValue(3, email, QSql::In);
	query.bindValue(4, name, QSql::In);
	query.exec();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
}

bool MySQLFactory::userExists(const QString & username, const QString & email, bool* userExists, bool* emailExists)
{
	QSqlQuery query;
	query.prepare("CALL spUserExists(?,?,@usr, @eml)");
	query.bindValue(0, username, QSql::In);
	query.bindValue(1, email, QSql::In);
	query.exec();
	query.exec("SELECT @usr, @eml");
	query.next();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);

	*userExists = query.value(0).toBool();
	*emailExists = query.value(1).toBool();

	return *userExists || *emailExists;
}

SqlObjectsModel * MySQLFactory::createTorrentsSqlModel()
{
	return new MySQLTorrents();
}

SqlObjectsModel * MySQLFactory::createUsersSqlModel()
{
	return new MySQLUsers();
}

ModelTorrent * MySQLFactory::createModelTorrent(const QString id)
{
	return new MySQLModelTorrent(id);
}

ModelUser * MySQLFactory::createModelUser(const QString id)
{
	return new MySQLModelUser(id);
}


QStringList MySQLFactory::getAllUsernames() const
{
	QStringList list;

	QSqlQuery query("SELECT username FROM User");

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);

	while (query.next())
	{
		list << query.value(0).toString();
	}

	return list;
}

bool MySQLFactory::passkeyExists(const QString & passkey, QString* username)
{
	QSqlQuery query;
	query.prepare("CALL spPasskeyExists(?,@v)");
	query.bindValue(0, passkey, QSql::In);
	query.exec();
	query.exec("SELECT @v");
	query.next();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);

	bool exists = query.value(0).toBool();

	if (!exists)
		return false;

	if (!username)
		return true;

	query.prepare("SELECT username FROM User WHERE passkey = ? INTO @v");
	query.bindValue(0, passkey, QSql::In);
	query.exec();
	query.exec("SELECT @v");
	query.next();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);

	*username = query.value(0).toString();

	return true;
}

QString MySQLFactory::createPasskey(const QString & username, const QString & password)
{
	SHA1Hash passkey;
	SHA1HashGen generator;

	int tryn = 0;

	while (TRUE)
	{

		//String na osnovu koga generisem passkey
		QString tmp = username + password + QTime::currentTime().toString();

		//Prvo generisem passkey
		passkey = generator.generate((const quint8*) tmp.toAscii().data(), tmp.toAscii().length());

		QSqlQuery query;
		query.prepare("CALL spPasskeyExists(?,@v)");
		query.bindValue(0, passkey.toString(), QSql::In);
		query.exec();
		query.exec("SELECT @v");
		query.next();

		if (query.lastError().type() != QSqlError::NoError)
			throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);

		if (tryn > 5) //odustani posle 5 generisanih passkey-eva
		{
			qWarning() << "Could not generate passkey more than 5 times!";
			throw;
		}

		if (!query.value(0).toBool()) //sve je ok passkey ne postoji
			break;
	}

	return passkey.toString();
}

void MySQLFactory::resetPasskey(const QString & username, const QString & password)
{
	QString newPasskey = createPasskey(username, password);

	QSqlQuery query;
	query.prepare("UPDATE User SET passkey = ? WHERE username = ?");
	query.bindValue(0, newPasskey, QSql::In);
	query.bindValue(1, username, QSql::In);
	query.exec();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
}

void MySQLFactory::changeUserInfo(const QString& username, const QString & password, const QString & email)
{
	QSqlQuery query;
	query.prepare("UPDATE User SET password = ?, email = ? WHERE username = ?");
	query.bindValue(0, password, QSql::In);
	query.bindValue(1, email, QSql::In);
	query.bindValue(2, username, QSql::In);
	query.exec();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
}

void MySQLFactory::updateTorrentDescription(const QString & infohash, const QString & newDescription) const
{
	QSqlQuery query;
	query.prepare("UPDATE Torrent SET description = ? WHERE INFO_HASH = ?");
	query.bindValue(0, newDescription, QSql::In);
	query.bindValue(1, infohash, QSql::In);
	query.exec();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
}

QAbstractItemModel* MySQLFactory::getTorrentListForUser(const QString & username, bool complete) const
{
	QSqlQueryModel* qm = new QSqlQueryModel();
	QSqlQuery qry;

	if (!complete)
		qry.prepare("select Name from Torrent where INFO_HASH in (select INFO_HASH from Announces where passkey in (select passkey from User where username = ?) and INFO_HASH not in (select INFO_HASH from Announces where event = 'completed' and passkey in (select passkey from User where username = ?)))");
	else
		qry.prepare("select Name from Torrent where INFO_HASH in (select INFO_HASH from Announces where passkey in (select passkey from User where username = ?) and INFO_HASH in (select INFO_HASH from Announces where event = 'completed' and passkey in (select passkey from User where username = ?)))");

	qry.bindValue(0, username, QSql::In);
	qry.bindValue(1, username, QSql::In);

	qry.exec();
	qm->setQuery(qry);

	if (qm->lastError().isValid())
		qDebug() << qm->lastError();

	return qm;
}

int MySQLFactory::usersOnline() const
{
	QSqlQuery query;
	query.prepare("CALL UsersOnline(@v)");
	query.exec();
	query.exec("SELECT @v");
	query.next();

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);

	return query.value(0).toInt();
}

void MySQLFactory::countPeers(const QString& infohash, int * seeders, int * leechers) const
{
	QSqlQuery query;
	query.prepare("CALL spCountPeers(?,@complete,@incomplete)");
	query.bindValue(0, infohash, QSql::In);

	query.exec();
	query.exec("SELECT @complete, @incomplete");
	query.next();
	
	if(query.lastError().type() != QSqlError::NoError || !query.isValid())
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);
	
	*seeders = query.value(0).toInt();
	*leechers = query.value(1).toInt();
}

void MySQLFactory::countTotalPeers(int * seeders, int * leechers) const
{
	QSqlQuery query("SELECT INFO_HASH FROM Torrent");

	if (query.lastError().type() != QSqlError::NoError)
		throw new DBException(query.lastError().text(), Exception::Critical, __FILE__, __FUNCTION__, __LINE__);

	int complete = 0;
	int incomplete = 0;
	while (query.next())
	{
		QString hash = query.value(0).toString();
		int s;
		int l;
		
		countPeers(hash, &s, &l);
		
		complete += s;
		incomplete += l;
	}
	
	*seeders = complete;
	*leechers = incomplete;
}
